/*
 * Copyright (c) 2001 Jakub Jermar
 * Copyright (c) 2005 Martin Decky
 * Copyright (c) 2011 Martin Sucha
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <abi/asmtool.h>
#include <arch/boot/boot.h>
#include <arch/boot/memmap.h>
#include <arch/mm/page.h>
#include <arch/pm.h>
#include <genarch/multiboot/multiboot.h>
#include <arch/cpuid.h>
#include <arch/cpu.h>

#define START_STACK  (BOOT_OFFSET - BOOT_STACK_SIZE)

.section K_TEXT_START, "ax"

.code32

.macro pm_error msg
	movl \msg, %esi
	jmp pm_error_halt
.endm

.macro pm_status msg
#ifdef CONFIG_EGA
	pushl %esi
	movl \msg, %esi
	call pm_early_puts
	popl %esi
#endif
.endm

.macro pm2_status msg
	pushl \msg
	call early_puts
.endm

.align 4
multiboot_header:
	.long MULTIBOOT_HEADER_MAGIC
	.long MULTIBOOT_HEADER_FLAGS
	.long -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)  /* checksum */
	.long multiboot_header
	.long unmapped_start
	.long 0
	.long 0
	.long multiboot_image_start

SYMBOL(multiboot_image_start)
	cli
	cld

	/* Initialize stack pointer */
	movl $START_STACK, %esp

	/*
	 * Initialize Global Descriptor Table and
	 * Interrupt Descriptor Table registers
	 */
	lgdtl bootstrap_gdtr
	lidtl bootstrap_idtr

	/* Kernel data + stack */
	movw $GDT_SELECTOR(KDATA_DES), %cx
	movw %cx, %es
	movw %cx, %fs
	movw %cx, %gs
	movw %cx, %ds
	movw %cx, %ss

	jmpl $GDT_SELECTOR(KTEXT_DES), $multiboot_meeting_point
	multiboot_meeting_point:

	/* Save multiboot arguments */
	movl %eax, multiboot_eax
	movl %ebx, multiboot_ebx

	pm_status $status_prot

#include "vesa_prot.inc"

#ifndef PROCESSOR_i486

	pm_status $status_prot2

	movl $(INTEL_CPUID_LEVEL), %eax
	cpuid
	cmp $0x0, %eax  /* any function > 0? */
	jbe pse_unsupported

	movl $(INTEL_CPUID_STANDARD), %eax
	cpuid
	bt $(INTEL_PSE), %edx
	jnc pse_unsupported

		/* Map kernel and turn paging on */
		pm_status $status_pse
		call map_kernel_pse
		jmp stack_init

#endif /* PROCESSOR_i486 */

	pse_unsupported:

		/* Map kernel and turn paging on */
		pm_status $status_non_pse
		call map_kernel_non_pse

	stack_init:

	/* Create the first stack frame */
	pushl $0
	movl %esp, %ebp

	pm2_status $status_prot3

	/* Call ia32_pre_main(multiboot_eax, multiboot_ebx) */
	pushl multiboot_ebx
	pushl multiboot_eax
	call ia32_pre_main

	pm2_status $status_main

	/* Call main_bsp() */
	call main_bsp

	/* Not reached */
	cli
	hlt0:
		hlt
		jmp hlt0

/** Setup mapping for the kernel (PSE variant)
 *
 * Setup mapping for both the unmapped and mapped sections
 * of the kernel. For simplicity, we map the entire 4G space.
 *
 */
FUNCTION_BEGIN(map_kernel_pse)
	/* Paging features */
	movl %cr4, %ecx
	orl $CR4_PSE, %ecx	/* PSE on */
	andl $~CR4_PAE, %ecx	/* PAE off */
	movl %ecx, %cr4

	movl $(page_directory + 0), %esi
	movl $(page_directory + 2048), %edi
	xorl %ecx, %ecx
	xorl %ebx, %ebx

	floop_pse:
		movl $(PDE_4M | PDE_RW | PDE_P), %eax
		orl %ebx, %eax
		/* Mapping 0x00000000 + %ecx * 4M => 0x00000000 + %ecx * 4M */
		movl %eax, (%esi, %ecx, 4)
		/* Mapping 0x80000000 + %ecx * 4M => 0x00000000 + %ecx * 4M */
		movl %eax, (%edi, %ecx, 4)
		addl $(4 * 1024 * 1024), %ebx

		incl %ecx
		cmpl $512, %ecx
		jl floop_pse

	movl %esi, %cr3

	movl %cr0, %ebx
	orl $CR0_PG, %ebx	/* paging on */
	movl %ebx, %cr0
	ret
FUNCTION_END(map_kernel_pse)

/** Setup mapping for the kernel (non-PSE variant).
 *
 * Setup mapping for both the unmapped and mapped sections
 * of the kernel. For simplicity, we map the entire 4G space.
 *
 */
FUNCTION_BEGIN(map_kernel_non_pse)
	/* Paging features */
	movl %cr4, %ecx
	andl $~CR4_PAE, %ecx  /* PAE off */
	movl %ecx, %cr4

	call calc_kernel_end
	call find_mem_for_pt

	mov kernel_end, %esi
	mov free_area, %ecx

	cmpl %esi, %ecx
	jbe use_kernel_end

		mov %ecx, %esi

		/* Align address down to 4k */
		andl $(~(PAGE_SIZE - 1)), %esi

	use_kernel_end:

		/* Align address to 4k */
		addl $(PAGE_SIZE - 1), %esi
		andl $(~(PAGE_SIZE - 1)), %esi

		/* Allocate space for page tables */
		movl %esi, pt_loc
		movl $KA2PA(ballocs), %edi

		movl %esi, (%edi)
		addl $4, %edi
		movl $(2 * 1024 * 1024), (%edi)

		/* Fill page tables */
		xorl %ecx, %ecx
		xorl %ebx, %ebx

		floop_pt:
			movl $(PTE_RW | PTE_P), %eax
			orl %ebx, %eax
			movl %eax, (%esi, %ecx, 4)
			addl $PAGE_SIZE, %ebx

			incl %ecx
			cmpl $(512 * 1024), %ecx

			jl floop_pt

		/* Fill page directory */
		movl $(page_directory + 0), %esi
		movl $(page_directory + 2048), %edi
		xorl %ecx, %ecx
		movl pt_loc, %ebx

		floop:
			movl $(PDE_RW | PDE_P), %eax
			orl %ebx, %eax

			/* Mapping 0x00000000 + %ecx * 4M => 0x00000000 + %ecx * 4M */
			movl %eax, (%esi, %ecx, 4)

			/* Mapping 0x80000000 + %ecx * 4M => 0x00000000 + %ecx * 4M */
			movl %eax, (%edi, %ecx, 4)
			addl $PAGE_SIZE, %ebx

			incl %ecx
			cmpl $512, %ecx

			jl floop

		movl %esi, %cr3

		movl %cr0, %ebx
		orl $CR0_PG, %ebx  /* paging on */
		movl %ebx, %cr0

		ret
FUNCTION_END(map_kernel_non_pse)

/** Calculate unmapped address of the end of the kernel. */
calc_kernel_end:
	movl $KA2PA(kdata_end), %edi
	movl %edi, kernel_end
	ret

/** Find free 2M (+4k for alignment) region where to store page tables */
find_mem_for_pt:
	/* Check if multiboot info is present */
	cmpl $MULTIBOOT_LOADER_MAGIC, multiboot_eax
	je check_multiboot_map

		ret

	check_multiboot_map:

		/* Copy address of the multiboot info to ebx */
		movl multiboot_ebx, %ebx

		/* Check if memory map flag is present */
		movl (%ebx), %edx
		andl $MULTIBOOT_INFO_FLAGS_MMAP, %edx
		jnz use_multiboot_map

			ret

	use_multiboot_map:

		/* Copy address of the memory map to edx */
		movl MULTIBOOT_INFO_OFFSET_MMAP_ADDR(%ebx), %edx
		movl %edx, %ecx

		addl MULTIBOOT_INFO_OFFSET_MMAP_LENGTH(%ebx), %ecx

		/* Find a free region at least 2M in size */
		check_memmap_loop:

			/* Is this a free region? */
			cmpl $MEMMAP_MEMORY_AVAILABLE, MULTIBOOT_MEMMAP_OFFSET_MM_INFO + E820MEMMAP_OFFSET_TYPE(%edx)
			jnz next_region

			/* Check size */
			cmpl $0, MULTIBOOT_MEMMAP_OFFSET_MM_INFO + E820MEMMAP_OFFSET_SIZE + 4(%edx)
			jnz next_region
			cmpl $(2 * 1024 * 1024 + PAGE_SIZE), MULTIBOOT_MEMMAP_OFFSET_MM_INFO + E820MEMMAP_OFFSET_SIZE(%edx)
			jbe next_region

			cmpl $0, MULTIBOOT_MEMMAP_OFFSET_MM_INFO + E820MEMMAP_OFFSET_BASE_ADDRESS + 4(%edx)
			jz found_region

		next_region:

			cmp %ecx, %edx
			jbe next_region_do

				ret

		next_region_do:

			addl MULTIBOOT_MEMMAP_OFFSET_SIZE(%edx), %edx
			addl $MULTIBOOT_MEMMAP_SIZE_SIZE, %edx
			jmp check_memmap_loop

		found_region:

			/* Use end of the found region */
			mov MULTIBOOT_MEMMAP_OFFSET_MM_INFO + E820MEMMAP_OFFSET_BASE_ADDRESS(%edx), %ecx
			add MULTIBOOT_MEMMAP_OFFSET_MM_INFO + E820MEMMAP_OFFSET_SIZE(%edx), %ecx
			sub $(2 * 1024 * 1024), %ecx
			mov %ecx, free_area

			ret

/** Print string to EGA display (in light red) and halt.
 *
 * Should be executed from 32 bit protected mode with paging
 * turned off. Stack is not required. This routine is used even
 * if CONFIG_EGA is not enabled. Since we are going to halt the
 * CPU anyway, it is always better to at least try to print
 * some hints.
 *
 * @param %esi NULL-terminated string to print.
 *
 */
pm_error_halt:
	movl $0xb8000, %edi  /* base of EGA text mode memory */
	xorl %eax, %eax

	/* Read bits 8 - 15 of the cursor address */
	movw $0x3d4, %dx
	movb $0xe, %al
	outb %al, %dx

	movw $0x3d5, %dx
	inb %dx, %al
	shl $8, %ax

	/* Read bits 0 - 7 of the cursor address */
	movw $0x3d4, %dx
	movb $0xf, %al
	outb %al, %dx

	movw $0x3d5, %dx
	inb %dx, %al

	/* Sanity check for the cursor on screen */
	cmp $2000, %ax
	jb err_cursor_ok

		movw $1998, %ax

	err_cursor_ok:

	movw %ax, %bx
	shl $1, %eax
	addl %eax, %edi

	err_ploop:
		lodsb

		cmp $0, %al
		je err_ploop_end

		movb $0x0c, %ah  /* black background, light red foreground */
		stosw

		/* Sanity check for the cursor on the last line */
		inc %bx
		cmp $2000, %bx
		jb err_ploop

		/* Scroll the screen (24 rows) */
		movl %esi, %edx
		movl $0xb80a0, %esi
		movl $0xb8000, %edi
		movl $960, %ecx
		rep movsl

		/* Clear the 24th row */
		xorl %eax, %eax
		movl $40, %ecx
		rep stosl

		/* Go to row 24 */
		movl %edx, %esi
		movl $0xb8f00, %edi
		movw $1920, %bx

		jmp err_ploop
	err_ploop_end:

	/* Write bits 8 - 15 of the cursor address */
	movw $0x3d4, %dx
	movb $0xe, %al
	outb %al, %dx

	movw $0x3d5, %dx
	movb %bh, %al
	outb %al, %dx

	/* Write bits 0 - 7 of the cursor address */
	movw $0x3d4, %dx
	movb $0xf, %al
	outb %al, %dx

	movw $0x3d5, %dx
	movb %bl, %al
	outb %al, %dx

	cli
	hlt1:
		hlt
		jmp hlt1

/** Print string to EGA display (in light green).
 *
 * Should be called from 32 bit protected mode with paging
 * turned off. A stack space of at least 24 bytes is required,
 * but the function does not establish a stack frame.
 *
 * Macros such as pm_status take care that this function
 * is used only when CONFIG_EGA is enabled.
 *
 * @param %esi NULL-terminated string to print.
 *
 */
pm_early_puts:
	pushl %eax
	pushl %ebx
	pushl %ecx
	pushl %edx
	pushl %edi

	movl $0xb8000, %edi  /* base of EGA text mode memory */
	xorl %eax, %eax

	/* Read bits 8 - 15 of the cursor address */
	movw $0x3d4, %dx
	movb $0xe, %al
	outb %al, %dx

	movw $0x3d5, %dx
	inb %dx, %al
	shl $8, %ax

	/* Read bits 0 - 7 of the cursor address */
	movw $0x3d4, %dx
	movb $0xf, %al
	outb %al, %dx

	movw $0x3d5, %dx
	inb %dx, %al

	/* Sanity check for the cursor on screen */
	cmp $2000, %ax
	jb pm_puts_cursor_ok

		movw $1998, %ax

	pm_puts_cursor_ok:

	movw %ax, %bx
	shl $1, %eax
	addl %eax, %edi

	pm_puts_ploop:
		lodsb

		cmp $0, %al
		je pm_puts_ploop_end

		movb $0x0a, %ah  /* black background, light green foreground */
		stosw

		/* Sanity check for the cursor on the last line */
		inc %bx
		cmp $2000, %bx
		jb pm_puts_ploop

		/* Scroll the screen (24 rows) */
		movl %esi, %edx
		movl $0xb80a0, %esi
		movl $0xb8000, %edi
		movl $960, %ecx
		rep movsl

		/* Clear the 24th row */
		xorl %eax, %eax
		movl $40, %ecx
		rep stosl

		/* Go to row 24 */
		movl %edx, %esi
		movl $0xb8f00, %edi
		movw $1920, %bx

		jmp pm_puts_ploop
	pm_puts_ploop_end:

	/* Write bits 8 - 15 of the cursor address */
	movw $0x3d4, %dx
	movb $0xe, %al
	outb %al, %dx

	movw $0x3d5, %dx
	movb %bh, %al
	outb %al, %dx

	/* Write bits 0 - 7 of the cursor address */
	movw $0x3d4, %dx
	movb $0xf, %al
	outb %al, %dx

	movw $0x3d5, %dx
	movb %bl, %al
	outb %al, %dx

	popl %edi
	popl %edx
	popl %ecx
	popl %ebx
	popl %eax

	ret

/** Print string to EGA display.
 *
 * Should be called from 32 bit protected mode (with paging
 * enabled and stack established). This function is ABI compliant.
 *
 * If CONFIG_EGA is undefined or CONFIG_FB is defined
 * then this function does nothing.
 *
 * @param %ebp+0x08 NULL-terminated string to print.
 *
 */
early_puts:

#if ((defined(CONFIG_EGA)) && (!defined(CONFIG_FB)))

	/* Prologue, save preserved registers */
	pushl %ebp
	movl %esp, %ebp
	pushl %ebx
	pushl %esi
	pushl %edi

	movl 0x08(%ebp), %esi
	movl $(PA2KA(0xb8000)), %edi  /* base of EGA text mode memory */
	xorl %eax, %eax

	/* Read bits 8 - 15 of the cursor address */
	movw $0x3d4, %dx
	movb $0xe, %al
	outb %al, %dx

	movw $0x3d5, %dx
	inb %dx, %al
	shl $8, %ax

	/* Read bits 0 - 7 of the cursor address */
	movw $0x3d4, %dx
	movb $0xf, %al
	outb %al, %dx

	movw $0x3d5, %dx
	inb %dx, %al

	/* Sanity check for the cursor on screen */
	cmp $2000, %ax
	jb early_puts_cursor_ok

		movw $1998, %ax

	early_puts_cursor_ok:

	movw %ax, %bx
	shl $1, %eax
	addl %eax, %edi

	early_puts_ploop:
		lodsb

		cmp $0, %al
		je early_puts_ploop_end

		movb $0x0e, %ah  /* black background, yellow foreground */
		stosw

		/* Sanity check for the cursor on the last line */
		inc %bx
		cmp $2000, %bx
		jb early_puts_ploop

		/* Scroll the screen (24 rows) */
		movl %esi, %edx
		movl $(PA2KA(0xb80a0)), %esi
		movl $(PA2KA(0xb8000)), %edi
		movl $960, %ecx
		rep movsl

		/* Clear the 24th row */
		xorl %eax, %eax
		movl $40, %ecx
		rep stosl

		/* Go to row 24 */
		movl %edx, %esi
		movl $(PA2KA(0xb8f00)), %edi
		movw $1920, %bx

		jmp early_puts_ploop
	early_puts_ploop_end:

	/* Write bits 8 - 15 of the cursor address */
	movw $0x3d4, %dx
	movb $0xe, %al
	outb %al, %dx

	movw $0x3d5, %dx
	movb %bh, %al
	outb %al, %dx

	/* Write bits 0 - 7 of the cursor address */
	movw $0x3d4, %dx
	movb $0xf, %al
	outb %al, %dx

	movw $0x3d5, %dx
	movb %bl, %al
	outb %al, %dx

	/* Epilogue, restore preserved registers */
	popl %edi
	popl %esi
	popl %ebx
	leave

#endif

	ret

#include "vesa_real.inc"

.section K_DATA_START, "aw", @progbits

.align 4096
page_directory:
	.space 4096, 0

SYMBOL(bootstrap_idtr)
	.word 0
	.long 0

SYMBOL(bootstrap_gdtr)
	.word GDT_SELECTOR(GDT_ITEMS)
	.long KA2PA(gdt)

SYMBOL(multiboot_eax)
	.long 0

SYMBOL(multiboot_ebx)
	.long 0

pt_loc:
	.long 0
kernel_end:
	.long 0
free_area:
	.long 0

status_prot:
	.asciz "[prot] "
status_pse:
	.asciz "[pse] "
status_non_pse:
	.asciz "[non_pse] "
status_vesa_copy:
	.asciz "[vesa_copy] "
status_multiboot_cmdline:
	.asciz "[multiboot_cmdline] "
status_vesa_real:
	.asciz "[vesa_real] "
status_prot2:
	.asciz "[prot2] "
status_prot3:
	.asciz "[prot3] "
status_main:
	.asciz "[main] "
